package com.ecar.mp.servlet;

import com.google.gson.Gson;
import com.sun.jersey.api.client.Client;
import com.sun.jersey.api.client.ClientResponse;
import com.sun.jersey.api.client.WebResource;
import com.sun.jersey.api.client.config.ClientConfig;
import com.sun.jersey.api.client.config.DefaultClientConfig;
import com.sun.jersey.api.json.JSONConfiguration;
import com.sun.jersey.core.util.MultivaluedMapImpl;
import com.ecar.common.dto.DataBean;
import com.ecar.common.exception.MyException;
import com.ecar.common.util.HttpUtils;
import com.ecar.common.util.JSONUtil;
import com.ecar.common.util.PropertyUtil;
import com.ecar.common.util.SpringContextUtil;
import com.ecar.common.util.StringUtil;
import com.ecar.mp.util.MemCacheUtil;

import net.sf.json.JSONObject;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import java.io.IOException;
import java.net.URLEncoder;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;

public class TransportServlet extends HttpServlet {

	private static final long serialVersionUID = -266565810999894402L;

	private static Logger log = LoggerFactory.getLogger(TransportServlet.class);

	private static final String TOKEN = "_token";
	private static final String USERDATA = "_userdata";

	private static MemCacheUtil mc = MemCacheUtil.getInstance();

	@Override
	public void destroy() { }

	@Override
	protected void doPut(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	@Override
	protected void doDelete(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	@Override
	protected void doGet(HttpServletRequest request, HttpServletResponse response)
			throws ServletException, IOException {
		doPost(request, response);
	}

	/**
	 * 
	 * 通常 rest 请求分为 以下几大类: 1. 以#/access 开头的 前端请求(包括注册, 登录, 注销请求) 2. 以#/app 开头的
	 * 控制台请求 3. 特殊请求 以#/download开头之类 等
	 * 
	 * 第1类请求不需要登录, 即无需校验 session, 而第2类请求必须检查session,如果session 为空 , 返回 419响应码,
	 * 由angular state管理转向登录;
	 * 
	 * 请求处理过程 如下: 1.解析各类参数 2.根据上面的请求类型做相应的处理 a.登录请求成功以后, 保存到session ,
	 * 注销请求成功以后则清除session信息 b.过滤掉特殊请求
	 * 
	 * 3.发送请求 如果session 不为空,则session数据会附带发送
	 */
	@Override
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException,
																						   IOException {
		UserData currentUser = null;
		Gson gs = new Gson();

		request.setCharacterEncoding("UTF-8");
		String type = request.getParameter("type");
		String url = request.getParameter("resource");
		String param = new String(request.getParameter("param").getBytes("UTF-8"), "UTF-8");
		String sessionid = request.getSession().getId();
		String ip = HttpUtils.getRemoteHost(request);

		PropertyUtil propertyUtil = (PropertyUtil) SpringContextUtil.getBean("propertyUtil");
		String REST_BASE_URL = propertyUtil.getValue("config.api.prefix");
		String SESSION_TIMEOUT = propertyUtil.getValue("config.session.timeout");

		JSONObject jsonObject = JSONObject.fromObject(param);
		String module = jsonObject.getString("module");
		String browser = jsonObject.containsKey("browser") ? jsonObject.getString("browser") : null;

		// 当前用户Session缓存
		Cache cache = DynaCacheService.getContent(sessionid + USERDATA);
		if (cache == null) {
			cache = (Cache) mc.get(sessionid + USERDATA);
			if (cache == null || cache.isExpired()) {
				cache = null;
			}
		}
		log.info("Current session id: {}, ip: {}", sessionid, ip);
		if (null != cache) {
			cache.setTimeOut(Long.parseLong(SESSION_TIMEOUT) * 1000 + new Date().getTime());
			mc.set(sessionid + USERDATA, cache, Integer.parseInt(SESSION_TIMEOUT));
			currentUser = (UserData) cache.getValue();
			log.info("Login staff: {}", currentUser.getUsername());
		} else {
			log.info("Open access.");
		}
		log.info("Call api: {}, params: {}", url, param);

		// 控制台请求需要做session 检查
		if (isConsoleURL(module) && null == currentUser && !isLogin(url)) {
			log.debug("++++++++++++++++++++session check fail++++++++++++++++++++");
			response.setStatus(419);
			return;
		} else if ("checkSession".equals(url) && currentUser != null) {
			ok(response, gs.toJson(currentUser));
			return;
		}

		if (isRefreshURL(url)) {// 从缓存获取session
			if (currentUser == null) currentUser = new UserData();
			ok(response, gs.toJson(currentUser));
			return;
		} else if (isConfigURL(url)){
			ok(response, configuration(propertyUtil));
			return;
		}

//		String accessToken;
//		if (isLogin(url)) {
//			try {
//				accessToken = cacheToken(sessionid + TOKEN, jsonObject);
//			} catch (MyException e) {
//				log.debug("登录用户授权失败，bad credentials。");
//				ok(response, unauthorized(e.getMessage()));
//				return;
//			}
//		} else {
//			accessToken = getCachedToken(sessionid + TOKEN);
//		}
//
//		if (StringUtil.isEmpty(accessToken)) {
//			log.info("++++++++++++++++++++token check fail++++++++++++++++++++");
//			clean(sessionid);
//			response.setStatus(401);
//			return;
//		}

		if (isLogout(url)) clean(sessionid);

		// 转发rest请求
		ClientResponse myresponse = send(REST_BASE_URL, type, url, jsonObject, ip, module, browser, currentUser);
		if (myresponse.getStatus() == 423) {
			log.debug("请求过于频繁。");
			tooSoon(response);
			return;
		} /*else if (myresponse.getStatus() >= 400) {
			clean(sessionid);
			response.setStatus(401);
			return;
		}*/

		String entity = myresponse.getEntity(String.class);
//		 限制下日志长度，超长日志不打印
		if (entity.length() < 2048) log.info(entity);

		if (isRefreshSession(url)) { // 强制刷新缓存
			mc.remove(sessionid + USERDATA);
			DynaCacheService.invalidate(sessionid + USERDATA);
			entity = authorized(sessionid, SESSION_TIMEOUT, entity);
		} else if (isLogin(url)) {
			entity = authorized(sessionid, SESSION_TIMEOUT, entity);
		}
		ok(response, entity);
	}

	/**
	 * @param urlString
	 * @param method
	 * @param resource
	 * @param param
	 * @return
	 * @throws IOException
	 */
	private ClientResponse send(String urlString,
								String method,
								String resource,
								JSONObject param,
								String ip,
								String module,
								String browser,
								UserData currentUser) throws IOException {

		ClientResponse response = null;

		ClientConfig config = new DefaultClientConfig();
		config.getFeatures().put(JSONConfiguration.FEATURE_POJO_MAPPING, Boolean.TRUE);
		Client c = Client.create(config);
		try {
			String url = urlString + URLEncoder.encode(resource, "UTF-8")
											   .replace("%2F", "/")
											   .replace("%3F", "?")
											   .replace("%3D", "=");
			log.info("call api: " + url);
			WebResource r = c.resource(url);

			MultivaluedMap<String, String> queryParams = new MultivaluedMapImpl();
//			queryParams.add("access_token", accessToken);
			queryParams.add("ip", ip);
			queryParams.add("module", module);
			queryParams.add("browser", browser);
			if (null != currentUser) queryParams.add("loginUser", String.valueOf(currentUser.getId()));

			if (method.equalsIgnoreCase("POST") || method.equalsIgnoreCase("ACTION")) {
				response = r.queryParams(queryParams)
							.entity(getEntity(param))
							.type(MediaType.APPLICATION_JSON)
							.accept(MediaType.APPLICATION_JSON)
							.post(ClientResponse.class);
			} else if (method.equalsIgnoreCase("GET")) {
				queryParams.add("param", URLEncoder.encode(param.toString(), "UTF-8").replace("+", "%20"));
				response = r.queryParams(queryParams)
							.type(MediaType.APPLICATION_JSON)
							.accept(MediaType.APPLICATION_JSON)
							.get(ClientResponse.class);
			} else if (method.equalsIgnoreCase("PUT")) {
				response = r.queryParams(queryParams)
							.entity(getEntity(param))
							.type(MediaType.APPLICATION_JSON)
							.accept(MediaType.APPLICATION_JSON)
							.put(ClientResponse.class);
			}
		} finally {
			c.destroy();
		}
		return response;
	}

	private String getEntity(JSONObject param) {
		JSONObject params = new JSONObject();
		params.put("fields", param.containsKey("fields") ? param.getJSONObject("fields") : null);
		params.put("datalist", param.containsKey("datalist") ? param.getJSONArray("datalist") : null);

		JSONObject entity = new JSONObject();
		entity.put("param", params);
		return entity.toString();
	}

	private boolean isConsoleURL(String module) {
		return StringUtils.contains(module, "#/app");
	}

	private boolean isLogin(String resource) {
		return StringUtils.contains(resource, "public/staff/login");
	}

	private boolean isLogout(String resource) {
		return StringUtils.contains(resource, "/logout");
	}

	private boolean isRefreshURL(String resource) {
		return StringUtils.contains(resource, "/getUserInSession");
	}

	private boolean isRefreshSession(String resource) {
		return StringUtils.contains(resource, "/forceFrash");
	}

	private boolean isConfigURL(String resource) {
		return StringUtils.contains(resource, "/configuration");
	}

	/**
	 * 转换登陆用户信息
	 * @param r
	 * @return
	 */
	private UserData getUserData(LinkedHashMap<String, Object> r) {
		UserData currentUser = new UserData();
		currentUser.setId(Integer.parseInt(r.get("id") == null ? "0" : r.get("id").toString()));
		currentUser.setUsername(r.get("username").toString());
		currentUser.setPhone(r.get("phone") == null ? null : r.get("phone").toString());
		currentUser.setEmail(r.get("email") == null ? null : r.get("email").toString());
		currentUser.setRoles((List<String>) (r.get("roles") == null ? new ArrayList<>() : r.get("roles")));
		currentUser.setAuths((List<String>) (r.get("auths") == null ? new ArrayList<>() : r.get("auths")));

		currentUser.setEmpno(r.get("empno") == null ? null : r.get("empno").toString());
		currentUser.setName(r.get("name") == null ? null : r.get("name").toString());
		currentUser.setGender(Integer.parseInt(r.get("gender") == null ? "0" : r.get("gender").toString()));
		currentUser.setDepartment(r.get("department") == null ? null : r.get("department").toString());
		currentUser.setPosition(r.get("position") == null ? null : r.get("position").toString());
		if (r.get("birth") != null)
			currentUser.setBirth(new Date(Long.parseLong(r.get("birth").toString())));
		return currentUser;
	}

	/**
	 * 获取缓存的token
	 * @param sessionId
	 * @return
	 */
	private String getCachedToken(String sessionId) {
		Token token = null;
		Cache tokenCache = TokenCacheService.getContent(sessionId);
		if (tokenCache == null) {
			tokenCache = (Cache) mc.get(sessionId);
			if (tokenCache.isExpired()) tokenCache = null;
		}
		if (tokenCache != null) {
			token = (Token) tokenCache.getValue();
			if (tokenCache.isExpired() || token.getExpires() < 1800) {
				PropertyUtil propertyUtil = (PropertyUtil) SpringContextUtil.getBean("propertyUtil");
				String refreshUrl = propertyUtil.getValue("oauth.refresh.url");
				refreshUrl = MessageFormat.format(refreshUrl, token.getRefreshToken());
				token = refreshToken(refreshUrl);
				if (token != null) {
					Cache c = TokenCacheService.putContent(sessionId, token, token.getExpires());
					mc.set(sessionId, c, token.getExpires());
				}
			}
		}

		return token != null ? token.getAccessToken() : null;
	}

	/**
	 * 获取token并缓存
	 * @param param
	 * @return
	 */
	private String cacheToken(String sessionId, JSONObject param) throws MyException {
		if (param.containsKey("fields")) {
			JSONObject fields = param.getJSONObject("fields");
			String username = fields.containsKey("username") ? fields.getString("username") : null;
			String password = fields.containsKey("password") ? fields.getString("password") : null;
			Token token = getToken(username, password);
			if (token != null) {
				Cache c = TokenCacheService.putContent(sessionId, token, token.getExpires());
				mc.set(sessionId, c, token.getExpires());
				return token.getAccessToken();
			}
		}

		return null;
	}

	/**
	 * 请求并返回授权token
	 * @param username
	 * @param password
	 * @return
	 */
	private Token getToken(String username, String password) throws MyException {
		if (StringUtil.isEmpty(username) || StringUtil.isEmpty(password)) return null;

		PropertyUtil propertyUtil = (PropertyUtil) SpringContextUtil.getBean("propertyUtil");
		String oauthUrl = propertyUtil.getValue("oauth.password.url");
		String refreshUrl = propertyUtil.getValue("oauth.refresh.url");

		oauthUrl = MessageFormat.format(oauthUrl, username, password);
		String response;
		JSONObject json;
		try {
			response = restClientGet(oauthUrl);
			json = JSONObject.fromObject(response);
		} catch (Exception e) {
			e.printStackTrace();
			json = new JSONObject();
		}
		if (json.containsKey("access_token")) return validateToken(json, refreshUrl);

		// Double check
		try {
			response = restClientGet(oauthUrl);
			json = JSONObject.fromObject(response);
		} catch (Exception e) {
			e.printStackTrace();
			json = new JSONObject();
		}
		if (json.containsKey("access_token")) return validateToken(json, refreshUrl);

		if (json.containsKey("error") && "Bad credentials".equals(json.getString("error_description")))
			throw new MyException("登录失败，账号或密码错误。");
		// We tried our best
		log.debug("授权失败："+json.toString());
		return null;
	}

	/**
	 * 验证token是否过期
	 * @param json
	 * @param refreshUrl
	 * @return
	 */
	private Token validateToken(JSONObject json, String refreshUrl) {
		int expires = json.getInt("expires_in");
		String accessToken = json.getString("access_token");
		String refreshToken = json.getString("refresh_token");
		String scope = json.getString("scope");

		if (expires <= 1800) {
			refreshUrl = MessageFormat.format(refreshUrl, refreshToken);
			return refreshToken(refreshUrl);
		} else {
			return new Token(expires, accessToken, refreshToken, scope);
		}
	}

	/**
	 * 刷新token
	 * @param refreshUrl
	 * @return
	 */
	private Token refreshToken(String refreshUrl) {
		log.debug("refresh expired token");
		String response = restClientGet(refreshUrl);
		JSONObject refreshed = JSONObject.fromObject(response);
		if (!refreshed.containsKey("access_token")) {
			log.debug("refresh token failed");
			log.debug(response);
			return null;
		}

		int expires = refreshed.getInt("expires_in");
		String refreshToken = refreshed.getString("refresh_token");
		String scope = refreshed.getString("scope");
		String accessToken = refreshed.getString("access_token");
		return new Token(expires, accessToken, refreshToken, scope);
	}

	private String restClientGet(String url) {
		Client c = Client.create(new DefaultClientConfig());
		ClientResponse response = null;
		String result = null;
		try {
			response = c.resource(url)
						.accept(MediaType.APPLICATION_JSON)
						.get(ClientResponse.class);
			result = response.getEntity(String.class);
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			if (response != null) response.close();
			c.destroy();
		}
		return result;
	}

	private void tooSoon(HttpServletResponse response) throws IOException {
		JSONObject entity = new JSONObject();
		entity.put("code", 423);
		entity.put("result", "操作过于频繁，请稍等重试。");
		response.setContentType("application/json");
		response.setCharacterEncoding("UTF-8");
		response.getWriter().write(entity.toString());
		response.setStatus(200);
	}

	/**
	 * http status 200
	 */
	private void ok(HttpServletResponse response, String entity) throws IOException {
		response.setContentType("application/json");
		response.setCharacterEncoding("UTF-8");
		response.getWriter().write(entity);
		response.setStatus(200);
	}

	/**
	 * 获取页面相关配置
	 */
	private String configuration(PropertyUtil propertyUtil) {
		String cos = propertyUtil.getValue("config.cos.url");
		String img = propertyUtil.getValue("config.cos.img");
		String apk = propertyUtil.getValue("config.cos.apk");
		JSONObject entity = new JSONObject();
		entity.put("host", cos);
		entity.put("img", img);
		entity.put("apk", apk);

		JSONObject result = new JSONObject();
		result.put("code", 200);
		result.put("result", entity);
		return result.toString();
	}

	/**
	 * 授权失败
	 */
	private String unauthorized(String message) {
		return "{\"code\":403,\"result\":\"" + message + "\"}";
	}

	/**
	 * 授权成功，解析用户信息，缓存用户session
	 */
	private String authorized(String sessionid, String sessionTimeout, String entity) {
		Gson gs = new Gson();
		DataBean dataBean = JSONUtil.deserialize(entity, DataBean.class);
		if (dataBean.getCode() == 200) {
			LinkedHashMap<String, Object> r = (LinkedHashMap<String, Object>) dataBean.getResult();
			if (r != null) {
				UserData currentUser = getUserData(r);
				Cache c = DynaCacheService.putContent(sessionid + USERDATA, currentUser, Long.parseLong(sessionTimeout));
				mc.set(sessionid + USERDATA, c, Integer.parseInt(sessionTimeout));
				return "{\"code\":200,\"result\":" + gs.toJson(currentUser) + "}";
			}
		}
		return JSONUtil.serialize(dataBean);
	}

	/**
	 * 清理session信息
	 * @param sessionid
	 */
	private void clean(String sessionid) {
		mc.remove(sessionid + USERDATA);
		mc.remove(sessionid + TOKEN);
		DynaCacheService.invalidate(sessionid + USERDATA);
		TokenCacheService.invalidate(sessionid + TOKEN);
	}
}
